package com.autumn.util.excel.workbook;

import com.autumn.util.CollectionUtils;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.excel.ExcelUtils;
import com.autumn.util.excel.WorkbookInfo;
import com.autumn.util.excel.YesOrNoConst;
import com.autumn.util.excel.column.ColumnInfo;
import com.autumn.util.excel.exceptions.ExcelException;
import com.autumn.util.excel.imports.ImportAdapterInfo;
import com.autumn.util.excel.imports.ImportInfo;
import com.autumn.util.excel.imports.ImportProperty;
import com.autumn.util.function.FunctionResult;
import com.autumn.util.tuple.TupleThree;
import com.autumn.util.tuple.TupleTwo;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 导入抽象
 *
 * @param <T>
 *            类型
 * @param <TImportInfo>
 *            导入信息
 * @param <TImportAdapterInfo>
 *            导入适配器
 * @param <TImportProperty>
 *            导入属性
 */
public abstract class AbstractWorkbookImport<T, TImportInfo extends ImportInfo<TImportAdapterInfo>, TImportAdapterInfo extends ImportAdapterInfo, TImportProperty extends ImportProperty> {

	private final Class<T> tClass;
	protected final WorkbookInfo workbookInfo;

	/**
	 * @return
	 */
	public Class<T> getTClass() {
		return tClass;
	}

	/**
	 * @param workbookInfo
	 * @param tClass
	 */
	public AbstractWorkbookImport(WorkbookInfo workbookInfo, Class<T> tClass) {
		this.tClass = tClass;
		this.workbookInfo = ExceptionUtils.checkNotNull(workbookInfo, "workbookInfo");
	}

	/**
	 * 导入数据
	 *
	 * @param imports
	 *            导入流
	 * @param sheetName
	 *            工作表名称,未指定则自动查找符合格式的首个表
	 * @return
	 */
	public List<T> imports(InputStream imports, String sheetName) {
		TImportInfo info = createImportInfo(imports, sheetName);
		List<T> items;
		if (info.getSheet().getLastRowNum() >= info.getBeginRowIndex()) {
			items = createItems(info.getSheet(), info.getBeginRowIndex(), info.getAdapters());
		} else {
			items = new ArrayList<>();
		}
		return items;
	}

	/**
	 * 创建实例委托
	 *
	 * @return
	 */
	protected abstract FunctionResult<T> createInstanceFactory();

	/**
	 * 创建适配器信息
	 *
	 * @param columnIndex
	 *            列索引
	 * @param importProperty
	 *            导入属性
	 * @return
	 */
	protected abstract TImportAdapterInfo createAdapterInfo(int columnIndex, TImportProperty importProperty);

	/**
	 * 检查导入列
	 *
	 * @param importMap
	 * @param sheet
	 * @return
	 */
	private TupleTwo<List<TImportAdapterInfo>, Integer> checkColumns(Map<String, TImportProperty> importMap,
			Sheet sheet) {
		int beginRowIndex = 0;
		List<TImportAdapterInfo> items = new ArrayList<TImportAdapterInfo>();
		for (int r = 0; r < sheet.getLastRowNum(); r++) {
			Row row = sheet.getRow(r);
			for (int c = 0; c < row.getPhysicalNumberOfCells(); c++) {
				Cell cell = row.getCell(c);
				if (ExcelUtils.isMergedCell(cell.getSheet(), cell.getRowIndex(), cell.getColumnIndex())
						|| !cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.STRING)) {
					break;
				}
				String columnName = cell.getStringCellValue().trim();
				TImportProperty importProperty = importMap.get(columnName);
				if (importProperty != null) {
					items.add(createAdapterInfo(c, importProperty));
				}
			}
			beginRowIndex++;
			if (CollectionUtils.any(items, (importAdapterInfo) -> importAdapterInfo != null)) {
				break;
			}
		}
		return new TupleTwo<>(items, beginRowIndex);
	}

	/**
	 * 查找导入列
	 *
	 * @param workbook
	 * @param sheetName
	 * @param importMap
	 * @return
	 */
	private TupleThree<List<TImportAdapterInfo>, Sheet, Integer> findImportColumns(Workbook workbook, String sheetName,
			Map<String, TImportProperty> importMap) {
		if (workbook.getNumberOfSheets() <= 0) {
			throw new ExcelException("导入的 Excel 文件至少需要一个工作表");
		}
		List<TImportAdapterInfo> adapters = null;
		Sheet sheet = null;
		int beginRowIndex = 0;
		TupleTwo<List<TImportAdapterInfo>, Integer> tuple2 = null;
		if (!StringUtils.isNullOrBlank(sheetName)) {
			sheet = workbook.getSheet(sheetName);
			if (sheet == null) {
				throw new ExcelException(String.format("Excel 文件不存在表 %s", sheetName));
			}
			tuple2 = checkColumns(importMap, sheet);
			beginRowIndex = tuple2.getItem2();
			adapters = tuple2.getItem1();
			if (adapters.size() == 0) {
				throw new ExcelException(String.format("Excel 文件表 %s 不存在对应的模板或模板格式不正确。", sheetName));
			}
		} else {
			for (int i = 0, j = workbook.getNumberOfSheets(); i < j; i++) {
				sheet = workbook.getSheetAt(i);
				tuple2 = checkColumns(importMap, sheet);
				beginRowIndex = tuple2.getItem2();
				adapters = tuple2.getItem1();
				if (CollectionUtils.any(adapters, (importAdapterInfo) -> importAdapterInfo != null)) {
					break;
				}
			}
			if (!CollectionUtils.any(adapters, (importAdapterInfo) -> importAdapterInfo != null)) {
				throw new ExcelException("自动在 Excel 文件中未找到符合模板的工作表。");
			}
		}
		TupleThree<List<TImportAdapterInfo>, Sheet, Integer> tuple3 = new TupleThree<List<TImportAdapterInfo>, Sheet, Integer>(
				adapters, sheet, beginRowIndex);
		return tuple3;
	}

	/**
	 * 创建导入属性Map
	 *
	 * @param importColumns
	 *            导入列
	 * @return
	 */
	protected abstract Map<String, ImportProperty> createImportPropertyMap(List<ColumnInfo> importColumns);

	/**
	 * 导入列
	 *
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Map<String, TImportProperty> importMap() {
		List<ColumnInfo> columns = workbookInfo.columnInfos();
		if (columns.size() == 0) {
			throw new ExcelException("导入模板至少需要一个导入列。");
		}
		if (CollectionUtils.any(columns, (columnInfo) -> StringUtils.isNullOrBlank(columnInfo.getPropertyName())
				|| StringUtils.isNullOrBlank(columnInfo.getFriendlyName()))) {
			throw new ExcelException("配置列信息中存在 PropertyName 或 FriendlyName 属性为值 null 或空白值。");
		}
		return (Map<String, TImportProperty>) createImportPropertyMap(columns);
	}

	/**
	 * 创建导入信息
	 *
	 * @param imports
	 * @param sheetName
	 * @return
	 */
	private TImportInfo createImportInfo(InputStream imports, String sheetName) {
		ExceptionUtils.checkNotNull(imports, "imports");
		Map<String, TImportProperty> importMap = importMap();
		Workbook workbook = ExcelUtils.createWorkbook(imports);
		if (workbook.getNumberOfSheets() <= 0) {
			throw new ExcelException("导入的 Excel 文件至少需要一个工作表");
		}
		int beginRowIndex = 0;
		Sheet sheet;
		TupleThree<List<TImportAdapterInfo>, Sheet, Integer> tuple3 = findImportColumns(workbook, sheetName, importMap);
		List<TImportAdapterInfo> adapters = tuple3.getItem1();
		beginRowIndex = tuple3.getItem3();
		sheet = tuple3.getItem2();
		for (Entry<String, TImportProperty> entry : importMap.entrySet()) {
			ColumnInfo columninfo = entry.getValue().getColumnInfo();
			if (columninfo.isImportNotNullable() && !CollectionUtils.any(adapters, (adapterInfo) -> {
				return adapterInfo.getColumnInfo().equals(columninfo);
			})) {
				throw new ExcelException(String.format("Excel 文件模板缺少必须列[%s]", entry.getKey()));
			}
		}
		return createImportInfo(sheet, adapters, beginRowIndex);
	}

	/**
	 * 创建导入信息
	 *
	 * @param sheet
	 * @param adapters
	 * @param beginRowIndex
	 * @return
	 */
	protected abstract TImportInfo createImportInfo(Sheet sheet, List<TImportAdapterInfo> adapters, int beginRowIndex);

	/**
	 * 获取单元格的值
	 *
	 * @param sheet
	 * @param cell
	 * @return
	 */
	protected Object getCellValue(Sheet sheet, Cell cell) {
		String value;
		HSSFFormulaEvaluator eva;
		if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.BOOLEAN)) {
			return cell.getBooleanCellValue();
		} else if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.NUMERIC)) {
			if (HSSFDateUtil.isCellDateFormatted(cell)) {
				return cell.getDateCellValue();
			}
			return cell.getNumericCellValue();
		} else if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.STRING)) {
			value = cell.getStringCellValue();
			if (StringUtils.isNullOrBlank(value)) {
				return null;
			}
			return value;
		} else if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.FORMULA)) {
			eva = new HSSFFormulaEvaluator((HSSFWorkbook) sheet.getWorkbook());
			return getCellValue(sheet, eva.evaluateInCell(cell));
		} else if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.ERROR)) {
			return null;
		} else if (cell.getCellType().equals(org.apache.poi.ss.usermodel.CellType.BLANK)) {
			return null;
		}
		return null;
	}

	/**
	 * 创建项目集合
	 *
	 * @param sheet
	 * @param beginRowIndex
	 * @param adapters
	 * @return
	 */
	private List<T> createItems(Sheet sheet, int beginRowIndex, List<TImportAdapterInfo> adapters) {
		List<T> items = new ArrayList<T>();
		FunctionResult<T> factory = createInstanceFactory();
		for (int r = beginRowIndex; r <= sheet.getLastRowNum(); r++) {
			T item = factory.apply();
			Row row = sheet.getRow(r);
			setItem(sheet, row, adapters, item);
			items.add(item);
		}
		return items;
	}

	/**
	 * 设置项目
	 *
	 * @param sheet
	 * @param row
	 * @param adapters
	 * @param item
	 */
	protected abstract void setItem(Sheet sheet, Row row, List<TImportAdapterInfo> adapters, T item);

	/**
	 * 获取单元格
	 *
	 * @param value
	 * @return
	 */
	protected Boolean cellBooleanValue(String value) {
		return YesOrNoConst.TRUE_SET.contains(value);
	}
}
